spring boot2整合shiro安全框架实现前后端分离的JWT token登录验证 您所在的位置:网站首页 shiro jwt 前后端分离 spring boot2整合shiro安全框架实现前后端分离的JWT token登录验证

spring boot2整合shiro安全框架实现前后端分离的JWT token登录验证

2024-07-04 05:42| 来源: 网络整理| 查看: 265

代码略多,粘贴一些关键的代码,完整demo当然必须放在GitHub上面啦,当然带SQL文件的,在项目里面

GitHub地址:  https://github.com/zhang-xiaoxiang/shiro-jwt

说明:由于初衷是解决自己项目的bug的,就找的网上的一面博客瞎搞了一个demo.然后报的错网上难以找到解决办法,后来自己解决了,就记录一下,所以不算教程,我看评论大伙说的修改密码后之前的token仍然有效,可以使用redis处理,或者其他业务逻辑代码处理一下,这个仅仅是一个demo,当然很多培训机构的源码也有一些demo,大家可以参考,这里主要是给需要的小伙伴一个思路或者遇到那个bug的解决方案

关于这个例子介绍:

1使用的是spring boot2作为环境基础

2数据库框架是mybatis plus(对mybatis熟悉就行,你顺便看一下mybatis plus逆向生成代码的魅力)

3包含全局异常的处理(报错getWriter() has already been called for this response),这个BUG是网上的访问量比较高的CSDN的博客案例留下的bug,为了解决这个bug才有我的这篇博客,所以记录一下

4shiro可以实现除了验证和授权之外的一些功能,这里主要展示验证,验证相当于登录,授权相当于登录后查看是否有权限,就想即使登录了支付宝,但是付款的时候还是需要验证支付宝密码一样,当然前后端未分离可以使用thymeleaf网页引擎,貌似黑马视频有,这里就只做个登录例子

5jwt这种方式是不太时尚的,正确的方式spring security oauth2实现认证授权,这里仅供参考,欢迎小伙伴提出宝贵意见,我都会尽快回复

6使用的是mysql8,pom和properties文件你改成现在用得多一点的5.7版本的写法就行了

7这里根据博友评论确实忘了用户修改密码token的处理,我相信这个大家应该会处理的(更新一下token就行了)

主要步骤:

首先是pom引入(主要的依赖,由于代码较大,而且我提供了Git地址,需要的直接从GitHub拉下来就行了,后面不做过多阐述)

com.auth0 java-jwt 3.4.0 org.apache.shiro shiro-spring 1.4.0

 

实现shiro的AuthenticationToken接口的类JwtToken //package com.example.shirojwt.jwt; import org.apache.shiro.authc.AuthenticationToken; /** * JwtToken:实现shiro的AuthenticationToken接口的类JwtToken * * @author zhangxiaoxiang * @date: 2019/07/12 */ public class JwtToken implements AuthenticationToken{ private String token; public JwtToken(String token) { this.token = token; } @Override public Object getPrincipal() { return token; } @Override public Object getCredentials() { return token; } }

 再写一个token的验证工具类

//package com.example.shirojwt.util; import com.auth0.jwt.JWT; import com.auth0.jwt.JWTVerifier; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.interfaces.DecodedJWT; import lombok.extern.slf4j.Slf4j; import java.util.Date; /** * JwtUtil:用来进行签名和效验Token * * @author zhangxiaoxiang * @date: 2019/07/12 */ @Slf4j public class JwtUtil { /** * JWT验证过期时间 EXPIRE_TIME 分钟 */ private static final long EXPIRE_TIME = 30 * 60 * 1000; /** * 校验token是否正确 * * @param token 密钥 * @param secret 用户的密码 * @return 是否正确 */ public static boolean verify(String token, String username, String secret) { try { //根据密码生成JWT效验器 Algorithm algorithm = Algorithm.HMAC256(secret); JWTVerifier verifier = JWT.require(algorithm) .withClaim("username", username) .build(); //效验TOKEN DecodedJWT jwt = verifier.verify(token); log.info("登录验证成功!"); return true; } catch (Exception exception) { log.error("JwtUtil登录验证失败!"); return false; } } /** * 获得token中的信息无需secret解密也能获得 * * @return token中包含的用户名 */ public static String getUsername(String token) { try { DecodedJWT jwt = JWT.decode(token); return jwt.getClaim("username").asString(); } catch (JWTDecodeException e) { return null; } } /** * 生成token签名EXPIRE_TIME 分钟后过期 * * @param username 用户名(电话号码) * @param secret 用户的密码 * @return 加密的token */ public static String sign(String username, String secret) { Date date = new Date(System.currentTimeMillis() + EXPIRE_TIME); Algorithm algorithm = Algorithm.HMAC256(secret); // 附带username信息 return JWT.create() .withClaim("username", username) .withExpiresAt(date) .sign(algorithm); } public static void main(String[] args) { /** * 测试生成一个token */ String sign = sign("18888888888", "123456"); log.warn("测试生成一个token\n"+sign); } }

写一个jwt过滤器来作为shiro的过滤器。

//package com.example.shirojwt.filter; import com.alibaba.fastjson.JSONObject; import com.alibaba.fastjson.serializer.SerializerFeature; import com.example.shirojwt.jwt.JwtToken; import com.example.shirojwt.result.ResponseData; import com.example.shirojwt.result.ResponseDataUtil; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * JwtFilter:jwt过滤器来作为shiro的过滤器 * * @author zhangxiaoxiang * @date: 2019/07/12 */ @Slf4j @Component//这个注入与否影响不大 public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter { /** * 执行登录 * @param request * @param response * @return * @throws Exception */ @Override protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String token = httpServletRequest.getHeader("Token"); JwtToken jwtToken = new JwtToken(token); // 提交给realm进行登入,如果错误他会抛出异常并被捕获 try { getSubject(request, response).login(jwtToken); // 如果没有抛出异常则代表登入成功,返回true return true; } catch (AuthenticationException e) { ResponseData responseData = ResponseDataUtil.authorizationFailed( "没有访问权限,原因是:" + e.getMessage()); //SerializerFeature.WriteMapNullValue为了null属性也输出json的键值对 Object o = JSONObject.toJSONString(responseData, SerializerFeature.WriteMapNullValue); response.setCharacterEncoding("utf-8"); response.getWriter().print(o); return false; } } /** * 执行登录认证 * * @param request * @param response * @param mappedValue * @return */ @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { try { return executeLogin(request, response); // return true;有一篇博客这里直接返回true是不正确的,在这里我特别指出一下 } catch (Exception e) { log.error("JwtFilter过滤验证失败!"); return false; } } /** * 对跨域提供支持 * @param request * @param response * @return * @throws Exception */ @Override protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpServletRequest = (HttpServletRequest) request; HttpServletResponse httpServletResponse = (HttpServletResponse) response; httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin")); httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE"); httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers")); // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态 if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) { httpServletResponse.setStatus(HttpStatus.OK.value()); return false; } return super.preHandle(request, response); } }

自定义Realm

//package com.example.shirojwt.shiro; import com.example.shirojwt.entity.User; import com.example.shirojwt.jwt.JwtToken; import com.example.shirojwt.service.UserService; import com.example.shirojwt.util.JwtUtil; import lombok.extern.slf4j.Slf4j; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * MyRealm:自定义一个授权 * * @author zhangxiaoxiang * @date: 2019/07/12 */ @Component @Slf4j public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; /** * 必须重写此方法,不然Shiro会报错 * @param token * @return */ @Override public boolean supports(AuthenticationToken token) { return token instanceof JwtToken; } /** * 只有当需要检测用户权限的时候才会调用此方法,例如checkRole,checkPermission之类的 * @param principals * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String username = JwtUtil.getUsername(principals.toString()); User user = userService.findByUserName(username); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); return simpleAuthorizationInfo; } /** * 默认使用此方法进行用户名正确与否验证,错误抛出异常即可。 * @param auth * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException { String token = (String) auth.getCredentials(); // 解密获得username,用于和数据库进行对比 String username = null; try { //这里工具类没有处理空指针等异常这里处理一下(这里处理科学一些) username = JwtUtil.getUsername(token); } catch (Exception e) { throw new AuthenticationException("heard的token拼写错误或者值为空"); } if (username == null) { log.error("token无效(空''或者null都不行!)"); throw new AuthenticationException("token无效"); } User userBean = userService.findByUserName(username); if (userBean == null) { log.error("用户不存在!)"); throw new AuthenticationException("用户不存在!"); } if (!JwtUtil.verify(token, username, userBean.getUserPassword())) { log.error("用户名或密码错误(token无效或者与登录者不匹配)!)"); throw new AuthenticationException("用户名或密码错误(token无效或者与登录者不匹配)!"); } return new SimpleAuthenticationInfo(token, token, "my_realm"); } }

 ShiroConfig核心配置

//package com.example.shirojwt.config; import com.example.shirojwt.filter.JwtFilter; import com.example.shirojwt.shiro.MyRealm; import org.apache.shiro.mgt.DefaultSessionStorageEvaluator; import org.apache.shiro.mgt.DefaultSubjectDAO; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import javax.servlet.Filter; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; /** * ShiroConfig:shiro 配置类,配置哪些拦截,哪些不拦截,哪些授权等等各种配置都在这里 * * 很多都是老套路,按照这个套路配置就行了 * * @author zhangxiaoxiang * @date: 2019/07/12 */ @Configuration public class ShiroConfig { /** * 注入安全过滤器 * @param securityManager * @return */ @Bean("shiroFilter") public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //拦截器 Map filterChainDefinitionMap = new LinkedHashMap(); // 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put("/login/**", "anon"); filterChainDefinitionMap.put("/**.js", "anon"); filterChainDefinitionMap.put("/druid/**", "anon"); filterChainDefinitionMap.put("/swagger**/**", "anon"); filterChainDefinitionMap.put("/**/swagger**/**", "anon"); filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/v2/**", "anon"); // 添加自己的过滤器并且取名为jwt Map filterMap = new HashMap(1); filterMap.put("jwt", new JwtFilter()); shiroFilterFactoryBean.setFilters(filterMap); //


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有